#ifndef __MEDUSA_RINGBUFFER_H__
#define __MEDUSA_RINGBUFFER_H__

#include "Buffer.h"

NS_MEDUSA_BEGIN

class RingBuffer
{
    
public:
    
    static RingBuffer* New(size_t buf_size, size_t expand_size =1024) {
        return new RingBuffer(buf_size, expand_size);
    }
    /*
     static RingBuffer* New(Buffer* buf_ptr) {
     return new RingBuffer(buf_ptr);
     }
     
     static RingBuffer* New(const RingBuffer* buf_ptr) {
     return new RingBuffer(buf_ptr);
     }
     
     static RingBuffer* New(Buffer& buf) {
     return new RingBuffer(buf);
     }
     
     static RingBuffer* New(const RingBuffer& buf) {
     return new RingBuffer(buf);
     }*/
    
public:
    
    virtual ~RingBuffer() {
        this->free_buffer();
    }
    
protected:
    
    RingBuffer(size_t buf_size, size_t expand=1024) {
        this->buffer_ptr    = new uint8_t[buf_size];
		this->buffer_size   = buf_size;
        this->expand_size   = expand;
		this->read_offset   = 0;
		this->write_offset  = 0;
    }
    
    RingBuffer(Buffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->Assign(buf_ptr);
    }
    
    RingBuffer(const RingBuffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->Assign(buf_ptr);
    }
    
    RingBuffer(Buffer& buf) {
        this->Assign(&buf);
    }
    
    RingBuffer(const RingBuffer& buf) {
        this->Assign(&buf);
    }
    
    RingBuffer& operator = (Buffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->Assign(buf_ptr);
        return *this;
    }
    
    RingBuffer& operator = (const RingBuffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->Assign(buf_ptr);
        
        return *this;
    }
    
    RingBuffer& operator = (Buffer& buf) {
        this->Assign(&buf);
        return *this;
    }
    
    RingBuffer& operator = (const RingBuffer& buf) {
        this->Assign(&buf);
        return *this;
    }
    
public:
    
    void Assign(Buffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->free_buffer();
        
        this->buffer_ptr = new uint8_t[buf_ptr->BufferSize()];
        this->buffer_size = buf_ptr->BufferSize();
        this->expand_size = buf_ptr->ExpandSize();
        this->read_offset = 0;
        this->write_offset = 0;
        while(!this->IsEmpty()) {
            this->WriteInt8(buf_ptr->ReadInt8());
        }
    }
    
    void Assign(const RingBuffer* buf_ptr) {
        MEDUSA_ASSERT (buf_ptr!=NULL);
        
        this->free_buffer();
        
        this->buffer_ptr = new uint8_t[buf_ptr->buffer_size];
        this->buffer_size = buf_ptr->buffer_size;
        this->expand_size = buf_ptr->expand_size;
        this->read_offset = buf_ptr->read_offset;
        this->write_offset = buf_ptr->write_offset;
        ::memcpy(this->buffer_ptr, buf_ptr->buffer_ptr, buf_ptr->buffer_size);
    }
    
    RingBuffer* Clone() const {
        RingBuffer* pbuf = new RingBuffer(this);
        return pbuf;
    }
    
    
private:
    void free_buffer() {
        if(this->buffer_ptr!=NULL) {
            delete []this->buffer_ptr;
            this->buffer_ptr = NULL;
            this->buffer_size = 0;
            this->read_offset = 0;
            this->write_offset = 0;
        }
    }
    
public:
    
    bool IsFull() const {
        return (((this->write_offset + 1) %
                 this->buffer_size) == this->read_offset);
    }
    
    bool IsEmpty() const {
        return (this->read_offset == this->write_offset);
    }
    
    void Clear() {
        this->read_offset = this->write_offset = 0;
    }
    
    size_t BufferSize() const {
        return this->buffer_size;
    }
    
    size_t ExpandSize() const {
        return this->expand_size;
    }
    
    size_t DataSize() const {
        if (this->write_offset == this->read_offset)
			return 0;
        
		return (this->write_offset > this->read_offset) ?
        (this->write_offset - this->read_offset)
        : (this->buffer_size - this->read_offset + this->write_offset);
    }
    
    size_t FreeSize() const {
        if (this->write_offset == this->read_offset)
			return this->buffer_size;
        
		return (this->write_offset > this->read_offset)
        ?
        (this->buffer_size- this->write_offset + this->read_offset)
        : (this->read_offset - this->write_offset);
    }
    
    size_t LineDataSize() const {
        if (this->write_offset == this->read_offset)
			return 0;
        
		return (this->write_offset > this->read_offset) ?
        (this->write_offset - this->read_offset)
        : (this->buffer_size - this->read_offset);
    }
    
    size_t LineFreeSize() const {
        if (this->write_offset == this->read_offset)
			return this->buffer_size - this->write_offset;
        
		return (this->write_offset > this->read_offset) ?
        (this->buffer_size - this->write_offset)
        : (this->read_offset - this->write_offset);
    }
    
public:
    
    // Int8
    uint8_t ReadInt8() {
        uint8_t ret = 0;
        if(!this->IsEmpty()){
            ret = this->buffer_ptr[this->read_offset];
            this->add_read_offset(1);
        }
		return ret;
    }
    
    void WriteInt8(uint8_t val) {
        if(this->IsFull()) {
            size_t new_buff_size = this->buffer_size + this->expand_size;
			uint8_t *new_buffer = new uint8_t[new_buff_size];
            uint8_t *old_buffer = this->buffer_ptr;
            //size_t data_size = this->DataSize();
            size_t size=0;
            while(!this->IsEmpty()) {
                new_buffer[size] = this->ReadInt8();
                size++;
            }
			this->buffer_ptr = new_buffer;
			this->buffer_size = new_buff_size;
            delete []old_buffer;
            this->read_offset = 0;
            this->write_offset = size;
        }
        this->buffer_ptr[this->write_offset] = val;
        this->add_write_offset(1);
    }
    
protected:
    
    // offset
    int add_read_offset(int offset) {
		this->read_offset = ((this->read_offset + offset) % this->buffer_size);
        
		return this->read_offset;
	}
    
	int add_write_offset(int offset) {
		this->write_offset = ((this->write_offset + offset) % this->buffer_size);
        
		return this->write_offset;
	}
    
private:
    size_t buffer_size;
    size_t expand_size;
    size_t read_offset;
    size_t write_offset;
    uint8_t *buffer_ptr;
    
};

NS_MEDUSA_END

#endif //__MEDUSA_RINGBUFFER_H__
